﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using Client.Threads;
using CommonTypes.Client;
using CommonTypes.Server;

namespace Client.Model
{
    public class ClientData
    {
        private readonly String _clientId;

        private ArrayList _clientSlots;

        private Dictionary<String, IClientToServer> _serversProxies;

        private Dictionary<long, ReservationInitiatorThread> _initiatedReservations;

        private Dictionary<long, ReservationClientThread> _initiatedClientReservations;

        private Dictionary<long, Reservation> _waitingResumeReservations;

        private Dictionary<DateTime, Reservation> _nonInitiatedReservations;

        private bool _GUIPresent;

        private bool _isConnected;

        private object _lockingObject;

        private IClientToServer _choosenServer;

        public ClientData( String clientId, bool GUI )
        {
            _clientId = clientId;
            _GUIPresent = GUI;
            _isConnected = false;

            _clientSlots = ArrayList.Synchronized( new ArrayList( ) );
            for( int index = 1; index <= 8760; index++ )
            {
                _clientSlots.Add( new CalendarSlot( index ) );
            }

            _serversProxies = new Dictionary<String, IClientToServer>( );
            _initiatedReservations = new Dictionary<long, ReservationInitiatorThread>( );
            _initiatedClientReservations = new Dictionary<long, ReservationClientThread>( );
            _waitingResumeReservations = new Dictionary<long, Reservation>( );
            _nonInitiatedReservations = new Dictionary<DateTime, Reservation>( );
            _lockingObject = new Object( );
            _choosenServer = null;
        }

        public String ClientId
        {
            get
            {
                return _clientId;
            }
        }

        public bool IsGUIPresent
        {
            get
            {
                return _GUIPresent;
            }
        }

        public ArrayList ClientSlots
        {
            get
            {
                return _clientSlots;
            }
        }

        public bool IsConnected
        {
            get
            {
                lock( _lockingObject )
                {
                    return _isConnected;
                }
            }
            set
            {
                lock( _lockingObject )
                {
                    _isConnected = value;
                }
            }
        }

        public Dictionary<String, IClientToServer> ServersProxies
        {
            get
            {
                return _serversProxies;
            }
        }

        public Dictionary<long, ReservationInitiatorThread> InitiatedReservations
        {
            get
            {
                lock( _lockingObject )
                {
                    return _initiatedReservations;
                }
            }
        }

        public Dictionary<long, ReservationClientThread> InitiatedClientReservations
        {
            get
            {
                lock( _lockingObject )
                {
                    return _initiatedClientReservations;
                }
            }
        }

        public Dictionary<DateTime, Reservation> NonInitiatedReservations
        {
            get
            {
                return _nonInitiatedReservations;
            }
        }

        private bool TestServer(IClientToServer icts)
        {
            try
            {
                if (icts.active())
                    return true;
            }
            catch
            {
                return false;
            }
            return true;
        }

        public IClientToServer GetRandomServer( )
        {
            if (_choosenServer == null || !TestServer(_choosenServer))
            {
                using (RNGCryptoServiceProvider random = new RNGCryptoServiceProvider())
                {
                    List<int> downServers = new List<int>();
                    byte[] data = new byte[4];
                    random.GetBytes(data);
                    int rand = Math.Abs(BitConverter.ToInt32(data, 0) % 4);
                    IClientToServer server = _serversProxies.ElementAt(rand).Value;
                    while (true)
                    {
                        if (TestServer(server))
                            break;
                        else
                        {       
                            downServers.Add(rand);
                            do
                            {
                                random.GetBytes(data);
                                rand = Math.Abs(BitConverter.ToInt32(data, 0) % 4);
                            } while (downServers.Contains(rand));

                            server = _serversProxies.ElementAt(rand).Value;
                        }
                    }
                    _choosenServer = server;
                    return server;
                }
            }
            else
                return _choosenServer;
        }

        public void AddServerProxy( string serverId, IClientToServer server )
        {
            _serversProxies.Add( serverId, server );
        }

        public void AddNonInitiatedReservation( Reservation reservation )
        {
            NonInitiatedReservations.Add( reservation.ReservationTimestamp, reservation );
        }

        public void AddReservationThread( ReservationInitiatorThread reservationThread )
        {
            lock( _lockingObject )
            {
                InitiatedReservations.Add( reservationThread.Reservation.TicketNumber, reservationThread );
            }
        }

        public ReservationInitiatorThread GetReservationThread( long ticketNumber )
        {
            lock( _lockingObject )
            {
                return _initiatedReservations[ ticketNumber ];
            }
        }

        public void AddReservationClientThread( long ticketNumber, ReservationClientThread reservationClientThread )
        {
            lock( _lockingObject )
            {
                if( !InitiatedClientReservations.ContainsKey( ticketNumber ) )
                {
                    InitiatedClientReservations.Add( ticketNumber, reservationClientThread );
                }
                else
                {
                    ReservationClientThread thread = InitiatedClientReservations[ ticketNumber ];
                    Request request = new Request( RequestPhase.ABORT, 0 );
                    thread.AddInitiatorRequest( request );
                    InitiatedClientReservations.Remove( ticketNumber );
                    InitiatedClientReservations.Add( ticketNumber, reservationClientThread );
                }
            }
        }

        public ReservationClientThread GetReservationClientThread( long ticketNumber )
        {
            lock( _lockingObject )
            {
                if( _initiatedClientReservations.ContainsKey( ticketNumber ) )
                    return _initiatedClientReservations[ ticketNumber ];
                else
                    return null;
            }
        }

        public bool TrySlotReservation( long slotNumber, Reservation reservation )
        {
            CalendarSlot clientActualSlot = ( CalendarSlot )_clientSlots[ ( int )slotNumber - 1 ];
            return clientActualSlot.AddClientReservation( reservation );
        }

        public Reservation SetReservationTicket( DateTime clientNonce, long ticketNumber )
        {
            Reservation reservation = _nonInitiatedReservations[ clientNonce ];
            reservation.TicketNumber = ticketNumber;
            return reservation;
        }

        public bool TryTentativelyBooked( long slotNumber, long ticketNumber )
        {
            CalendarSlot tryingSlot = ( CalendarSlot )_clientSlots[ ( int )slotNumber - 1 ];
            return tryingSlot.AddTentativelyBooked( ticketNumber );
        }

        public bool ProcessDoCommit( long slotNumber, long ticketNumber )
        {
            CalendarSlot tryingSlot = ( CalendarSlot )_clientSlots[ ( int )slotNumber - 1 ];
            return tryingSlot.SlotDoCommit( ticketNumber );
        }

        public bool ProcessVoteCommit( long slotNumber, long ticketNumber )
        {
            CalendarSlot tryingSlot = ( CalendarSlot )_clientSlots[ ( int )slotNumber - 1 ];
            return tryingSlot.SlotCommitRequest( ticketNumber );
        }

        public void RemoveTbReservation( long slotNumber, long ticketNumber )
        {
            CalendarSlot removingSlot = ( CalendarSlot )_clientSlots[ ( int )slotNumber - 1 ];
            removingSlot.RemoveTbReservation( ticketNumber );
        }

        public void StopInitiatedReservations( )
        {
            foreach( ReservationInitiatorThread initiatorThread in _initiatedReservations.Values )
            {
                initiatorThread.Kill( );
                Reservation reservation = initiatorThread.Reservation;
                _waitingResumeReservations.Add( reservation.TicketNumber, reservation );
            }
        }

        public void ResumeBreakReservations( )
        {
            foreach( Reservation reservation in _waitingResumeReservations.Values )
            {
                ReservationInitiatorThread reservationThreadObject = _initiatedReservations[ reservation.TicketNumber ];
                //this.AddReservationThread(reservationThreadObject);

                ThreadStart reservationThreadStart = new ThreadStart( reservationThreadObject.Run );
                Thread reservationThread = new Thread( reservationThreadStart );
                reservationThread.IsBackground = true;
                reservationThread.Start( );
            }
            _waitingResumeReservations.Clear( );
        }
    }
}